package raft

import "math"

func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {
	rf.mu.Lock()
	defer rf.mu.Unlock()
	defer rf.persist()
	if args.Term < rf.currentTerm {
		reply.Success = false
		reply.Term = rf.currentTerm
		return
	} else if args.Term > rf.currentTerm {
		rf.currentTerm = args.Term
		rf.Convert2Follower(args.Term)
	}
	rf.electionTimeStamp = rf.GetNow()
	LastestIndex, _ := rf.LastestEntryInfo()
	if LastestIndex < args.PrevLogIndex {
		reply.Success = false
		reply.Term = rf.currentTerm
		reply.ConflictIndex = len(rf.log)
		reply.ConflictTerm = -1
		return
	}
	if rf.log[args.PrevLogIndex].Term != args.PrevLogTerm {
		reply.Success = false
		reply.Term = rf.currentTerm
		reply.ConflictTerm = rf.log[args.PrevLogIndex].Term
		reply.ConflictIndex = args.PrevLogIndex
		for i := args.PrevLogIndex; i >= 1; i-- {
			if rf.log[i].Term == reply.ConflictTerm {
				reply.ConflictIndex = i
			} else {
				break
			}
		}
		return
	}

	entriesBegin := args.PrevLogIndex + 1
	for i := entriesBegin; i < entriesBegin+len(args.Entries); i++ {
		entry := args.Entries[i-entriesBegin]
		if i >= len(rf.log) {
			rf.log = append(rf.log, entry)
		} else if rf.log[i].Term != entry.Term {
			rf.log = rf.log[0:i]
			rf.log = append(rf.log, entry)
		}
	}

	oldCommitIndex := rf.commitIndex
	if args.LeaderCommit > rf.commitIndex {
		//DPrintf("node[%d]: Get bigger commitIndex %v > %v", rf.me, args.LeaderCommit, rf.commitIndex)
		LastestIndex, _ := rf.LastestEntryInfo()
		rf.commitIndex = int(math.Min(float64(args.LeaderCommit), float64(LastestIndex)))
	}

	reply.Term = rf.currentTerm
	reply.Success = true
	rf.applyToStateMachine(oldCommitIndex)
}

func (rf *Raft) sendAppendEntries(server int, args *AppendEntriesArgs, reply *AppendEntriesReply) bool {
	ok := rf.peers[server].Call("Raft.AppendEntries", args, reply)
	return ok
}

func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
	rf.mu.Lock()
	defer rf.mu.Unlock()
	defer rf.persist()
	DPrintf("node[%d]: RequestVote IN %v %v %v", rf.me, rf.currentTerm, rf.roleName(), args)
	defer DPrintf("node[%d]: RequestVote OUT %v %v", rf.me, args, reply)
	if args.Term < rf.currentTerm {
		reply.Term = rf.currentTerm
		reply.VoteGranted = false
		return
	} else if args.Term > rf.currentTerm {
		rf.currentTerm = args.Term
		rf.Convert2Follower(args.Term)
	}

	if rf.votedFor == -1 || rf.votedFor == args.CandidateId {
		if rf.HasUpToDateLogs(args.LastLogIndex, args.LastLogTerm) {
			reply.Term = rf.currentTerm
			reply.VoteGranted = true
			rf.electionTimeStamp = rf.GetNow()
			rf.votedFor = args.CandidateId
		} else {
			reply.Term = rf.currentTerm
			reply.VoteGranted = false
		}
	}
}

func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool {
	ok := rf.peers[server].Call("Raft.RequestVote", args, reply)
	return ok
}
